//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is free 
// software: you can redistribute it and/or modify it under the terms of 
// the GNU General Public License as published by the Free Software Foundation, 
// either version 3 of the License, or (at your option) any later version.

// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 
// in the hope that it will be useful, but with permitted additional restrictions 
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
// distributed with this program. You should have received a copy of the 
// GNU General Public License along with permitted additional restrictions 
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection

/* $Header:   F:\projects\c&c\vcs\code\reinf.cpv   2.17   16 Oct 1995 16:48:58   JOE_BOSTIC  $ */
/***********************************************************************************************
 ***             C O N F I D E N T I A L  ---  W E S T W O O D   S T U D I O S               *** 
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Command & Conquer                                            *
 *                                                                                             *
 *                    File Name : REINF.CPP                                                    *
 *                                                                                             *
 *                   Programmer : Joe L. Bostic                                                *
 *                                                                                             *
 *                   Start Date : May 24, 1994                                                 *
 *                                                                                             *
 *                  Last Update : July 4, 1995 [JLB]                                           *
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 *   Create_Air_Reinforcement -- Creates air strike reinforcement                              * 
 *   Create_Special_Reinforcement -- Ad hoc reinforcement handler.                             * 
 *   Do_Reinforcements -- Create and place a reinforcement team.                               * 
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include	"function.h"


/*********************************************************************************************** 
 * Do_Reinforcements -- Create and place a reinforcement team.                                 * 
 *                                                                                             * 
 *    This routine is called when a reinforcement team must be created and placed on the map.  * 
 *    It will create all members of the team and place them at the location determined from    * 
 *    the team composition. The reinforcement team should follow team orders until overriden   * 
 *    by AI or player intervention.                                                            * 
 *                                                                                             * 
 * INPUT:   teamtype -- Pointer to the team type to create as a reinforcement.                 * 
 *                                                                                             * 
 * OUTPUT:  Was the reinforcement successfully created and placed?                             * 
 *                                                                                             * 
 * WARNINGS:   none                                                                            * 
 *                                                                                             * 
 * HISTORY:                                                                                    * 
 *   05/08/1995 JLB : Created.                                                                 * 
 *   05/18/1995 JLB : Returns success or failure condition.                                    * 
 *   06/19/1995 JLB : Announces reinforcements.                                                * 
 *=============================================================================================*/
bool Do_Reinforcements(TeamTypeClass *teamtype)
{
	/*
	**	preform some preliminary checks for validity.
	*/
	if (!teamtype || !teamtype->ClassCount) return(false);

	/*
	**	Create the controlling team. All the objects are grouped
	**	under this team control. If there are no missions for this team
	**	then don't actually create the team -- it won't serve a purpose.
	*/
	TeamClass * team = NULL;
	if (teamtype->MissionCount) {
		team = new TeamClass(teamtype, HouseClass::As_Pointer(teamtype->House));
		if (!team) return(false);
		team->Force_Active();
	}

	/*
	**	Determine if this team contains its own transport. In such a case, the
	**	transport is used as a loaner. This is only true, if there is other
	**	objects to be transport. Without such cargo objects, then the transport
	**	is presumed to be the reinforcement itself and thus should not be a
	**	loaner.
	*/
	bool okvoice = true;						// Presume ok to announce reinforcement?
	bool airtransport = false;				// Transport can fly in?
	bool watertransport = false;			// Transport needs a beach to land at?
	bool onlytransport = true;				// Just transport is in reinforcement?
	bool hastransport = false;				// Group comes with transport?
	int index;
	for (index=0; index < teamtype->ClassCount; index++) {
		if (teamtype->Class[index]->IsTransporter || teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) {
			hastransport = true;
			if (teamtype->Class[index]->What_Am_I() == RTTI_AIRCRAFTTYPE) {
				airtransport = true;
			} else {
				watertransport = (((UnitTypeClass const *)teamtype->Class[index])->Type == UNIT_HOVER);
			}
		} else {
			onlytransport = false;
		}
	}

	/*
	**	Now determine how the reinforcement should be delivered. This is largely determined
	**	by whether there is a transport with the reinforcements.
	*/
	SourceType source = SOURCE_NONE;
	if (airtransport) {
		source = SOURCE_AIR;
	} else {

		if (watertransport) {
			source = SOURCE_BEACH;
		} else {
			
			/*
			**	Special case for the gunboat. It always arrives according to the shipping source.
			*/
			if (teamtype->Class[0]->What_Am_I() == RTTI_UNITTYPE && ((UnitTypeClass const *)teamtype->Class[0])->Type == UNIT_GUNBOAT) {
				source = SOURCE_SHIPPING;
			} else {
				source = HouseClass::As_Pointer(teamtype->House)->Edge;
			}
		}
	}

	/*
	**	If we can't determine where the reinforcement should come from, then delete it
	**	and return a failure condition.
	*/
	if (source == SOURCE_NONE) {
		if (team) delete team;
		return(false);
	}

	/*
	**	Now that the official source for the reinforcement has been determined, the 
	**	objects themselves must be created.
	*/
	TechnoClass * transport = NULL;
	TechnoClass * object = NULL;
	for (index = 0; index < teamtype->ClassCount; index++) {
		TechnoTypeClass const * tclass = teamtype->Class[index];

		for (int sub = 0; sub < teamtype->DesiredNum[index]; sub++) {
			ScenarioInit++;
			FootClass * temp = (FootClass *)tclass->Create_One_Of(HouseClass::As_Pointer(teamtype->House));
			ScenarioInit--;

			if (temp) {

				/*
				**	Add the object to the team. This is true even for the transports. The one
				**	exception is for the hover lander which never becomes part of the team.
				*/
				if (team && (temp->What_Am_I() != RTTI_UNIT || *((UnitClass*)temp) != UNIT_HOVER)) {
					ScenarioInit++;
					team->Add(temp);
					ScenarioInit--;
				}

				/*
				**	Build the list of transporters and passengers.
				*/
				if (tclass->IsTransporter && (!airtransport || tclass->What_Am_I() == RTTI_AIRCRAFTTYPE)) {

					/*
					**	Transports are considered loaners if they are transporting 
					**	something. They are presumed to only serve as a delivery
					**	agent.
					*/
					if (!onlytransport && temp->What_Am_I() != RTTI_UNIT) {
						temp->IsALoaner = true;
					}

					/*
					**	Link to the list of transports.
					*/
					temp->Next = transport;
					transport = temp;
				} else {

					/*
					**	A-10s are always considered loaners since the player should
					**	never be allowed to control them.
					*/
					if (temp->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)temp) == AIRCRAFT_A10) {
						temp->IsALoaner = true;
					}

					/*
					**	Link to the list of normal objects.
					*/
					temp->Next = object;
					object = temp;
				}
			}
		}
	}

	/*
	**	Bail on this reinforcement if no reinforcements could be created.
	**	This is probably because the object maximum was reached.
	*/
	if (!object && !transport) {
		if (team) delete team;
		return(false);
	}

	/*
	**	Now it is time to place the objects on the map. If there is a transport, then the
	**	transported objects must be placed inside the transport at this time as well.
	*/
	if (transport) {
		if (object) {

			/*
			**	For cargo planes that carry reinforcements, don't announce arrival
			**	when the transport is created. The announcement will occur when the
			**	transport unloads.
			*/
			if (transport->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass *)transport) == AIRCRAFT_CARGO) {
				okvoice = false;
			}

			transport->Attach((FootClass *)object);
		}
		object = transport;
	}

	/*
	**	Pick the location where the reinforcements appear and then place
	**	them there.
	*/
	bool placed = false;
	CELL cell = 0;
	FacingType eface;
	switch (source) {

		case SOURCE_SHIPPING:
			cell = Map.Calculated_Cell(source, teamtype->House);
			object->IsALoaner = true;
			if (object->Unlimbo(Cell_Coord(cell), DIR_W)) {
				object->Assign_Mission(MISSION_GUARD);
				object->Assign_Destination(::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(object->Coord)) )));
			} else {
				if (team) delete team;
				delete object;
				return(false);
			}
			break;

		case SOURCE_NORTH:
		case SOURCE_SOUTH:
		case SOURCE_EAST:
		case SOURCE_WEST: {
			eface = (FacingType)(source << 1);	// Facing to enter map.

			if (airtransport) ScenarioInit++;
			cell = Map.Calculated_Cell(source, teamtype->House);
			if (airtransport) ScenarioInit--;
			CELL newcell = cell;

			FootClass * o = (FootClass *)object->Next;
			object->Next = 0;
			bool ok = true;
			while (newcell > 0 && object) {
				DirType desiredfacing = Facing_Dir(eface);
				if (object->What_Am_I() == RTTI_AIRCRAFT) {
					desiredfacing = Random_Pick(DIR_N, DIR_MAX);
				}

				if (ok && object->Unlimbo(Cell_Coord(newcell), desiredfacing)) {

					/*
					**	If this object is part of a team, then the mission for this
					**	object will be guard. The team handler will assign the proper 
					**	mission that it should follow.
					*/
					if (object->What_Am_I() == RTTI_AIRCRAFT) {
						ok = false;
					} else {
						if (team) {
							object->Assign_Mission(MISSION_GUARD);
						} else {
							object->Assign_Mission(MISSION_MOVE);
							object->Assign_Destination(Adjacent_Cell(newcell, eface));
						}
						object->Commence();
					}

				} else {
					ok = true;
					
					/*
					**	Could not unlimbo at location specified so find an adjacent location that it can
					**	be unlimboed at. If this fails, then abort the whole placement process.
					*/
					FacingType adj;
					for (adj = FACING_N; adj < FACING_COUNT; adj++) {
						CELL trycell = Adjacent_Cell(newcell, adj);
						if (!Map.In_Radar(trycell) && object->Can_Enter_Cell(trycell, adj) == MOVE_OK) {
							newcell = trycell;
							break;
						}
					}
					if (adj < FACING_COUNT) continue;
					newcell = -1;
				}

				object = o;
				if (object) {
					o = (FootClass *)object->Next;
					object->Next = 0;
				}
			}

			/*
			**	If there are still objects that could not be placed, then delete them.
			*/
			if (o) {
				while (o) {
					FootClass * old = o;
					o = (FootClass *)o->Next;
					old->Next = 0;

					delete old;
				}

			}
		}
		break;

		/*
		**	Bring out the aircraft as separate "groups" of one.
		*/
		case SOURCE_AIR: {
			AircraftClass * thisone = (AircraftClass *)object;
			TARGET target = TARGET_NONE;
			while (thisone) {
				AircraftClass * next = (AircraftClass *)thisone->Next;

				/*
				**	Find a suitable map entry location. Cargo planes will try to find a cell that
				**	exactly lines up with the airfield they will unload at.
				*/
				COORDINATE newcoord;
				long reinforcement_delay = -1;
				ScenarioInit++;
				newcoord = Cell_Coord(Map.Calculated_Cell(HouseClass::As_Pointer(teamtype->House)->Edge, teamtype->House));
				ScenarioInit--;
				if (*thisone == AIRCRAFT_CARGO) {
					BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false);
					if (building) {
						COORDINATE docking_coord = building->Docking_Coord();
						const int border_x = Cell_To_Lepton(Map.MapCellX + Map.MapCellWidth) | 0x80;
						if (Special.ModernBalance) {
							/*
							** Cargo plane takes 5 seconds to reach the airstrip on Normal (1.5x legacy), or (75 / 10) seconds at speed.
							** Assumes a 45ms (1000 / 45 ticks per second) service rate.
							*/
							const int speed = AircraftTypeClass::As_Reference(AIRCRAFT_CARGO).MaxSpeed;
							int spawn_x = Coord_X(docking_coord) + ((speed * 1000 * 75) / (45 * 10));
							if (spawn_x > border_x) {
								reinforcement_delay = (spawn_x - border_x) / speed;
								spawn_x = border_x;
							}
							newcoord = XY_Coord(spawn_x, Coord_Y(docking_coord));
						} else {
							newcoord = XY_Coord(border_x, Coord_Y(docking_coord));
						}
						if (teamtype->MissionCount) {
							teamtype->MissionList[0].Argument = building->As_Target();
						}
					} 
				}
				thisone->Next = 0;

				ScenarioInit++;
				placed = thisone->Unlimbo(newcoord, DIR_W);
				if (Special.ModernBalance && reinforcement_delay >= 0) {
					thisone->Set_Reinforcement_Delay(reinforcement_delay);
				}
				ScenarioInit--;
				if (placed) {
					if (!team) {
						if (thisone->Class->IsFixedWing) {
							thisone->Assign_Mission(MISSION_HUNT);
							if (*thisone == AIRCRAFT_A10) {
								/*
								**	Groups of A10s always go after the same target initally.
								*/
								if (target == TARGET_NONE) {
									target = thisone->Greatest_Threat(THREAT_NORMAL);
								}
								thisone->Assign_Target(target);
							}
						} else {
							if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) {
								thisone->Assign_Mission(MISSION_UNLOAD);
							} else {
								thisone->Assign_Mission(MISSION_MOVE);
							}
							thisone->Assign_Destination(::As_Target(Map.Calculated_Cell(source, teamtype->House)));
						}
						thisone->Commence();
					}
				} else {
					delete thisone;
				}

				thisone = next;
			}
			if (!placed && team) delete team;


			/*
			**	Fixes bug that can happen if the reinforcement cannot be created.
			**	This prevent "phantom" teams and team types from being left around.
			*/
			if (GameToPlay == GAME_NORMAL && !placed) return(false);

		}
		break;

		case SOURCE_OCEAN:
		case SOURCE_BEACH:
			cell = Map.Calculated_Cell(SOURCE_BEACH, teamtype->House);
			if (cell) {
				CELL edge = XY_Cell(Cell_X(cell), Map.MapCellY+Map.MapCellHeight);

				placed = object->Unlimbo(Cell_Coord(edge), DIR_N);
				if (placed) {
					if (!team) {
						object->IsLocked = false;
						object->Assign_Mission(MISSION_UNLOAD);
						object->Commence();
						object->Assign_Destination(::As_Target(cell));
					}
				} else {
					if (team) delete team;
					delete object;
					return(false);
				}
			}
			break;

		default:
			break;
	}

	/*
	**	Announce when the reinforcements have arrived.
	*/
	if (okvoice && teamtype->House == PlayerPtr->Class->House) {
		Speak(VOX_REINFORCEMENTS, NULL, cell ? Cell_Coord(cell) : 0);
	}

	return(true);
}			


/*********************************************************************************************** 
 * Create_Special_Reinforcement -- Ad hoc reinforcement handler.                               * 
 *                                                                                             * 
 *    Use this routine to bring on a reinforcement that hasn't been anticipated by the trigger * 
 *    system. An example of this would be replacement harvesters or airfield ordered units.    * 
 *    The appropriate transport is created (if necessary) and a mission is assigned such that  * 
 *    the object will legally bring itself onto the playing field.                             * 
 *                                                                                             * 
 * INPUT:   house    -- The owner of this reinforcement.                                       * 
 *                                                                                             * 
 *          type     -- The object to bring on.                                                * 
 *                                                                                             * 
 *          another  -- This is reserved for the transport class in those cases where the      * 
 *                      transport MUST be forced to a specific type.                           * 
 *                                                                                             * 
 *          mission  -- The mission to assign this reinforcement team.                         * 
 *                                                                                             * 
 *          argument -- Optional team mission argument (usually a waypoint).                   * 
 *                                                                                             * 
 * OUTPUT:  Was the special reinforcement created without error?                               * 
 *                                                                                             * 
 * WARNINGS:   This routine will fail if a team type cannot be created.                        * 
 *                                                                                             * 
 * HISTORY:                                                                                    * 
 *   07/04/1995 JLB : Created.                                                                 * 
 *=============================================================================================*/
bool Create_Special_Reinforcement(HouseClass * house, TechnoTypeClass const * type, TechnoTypeClass const * another, TeamMissionType mission, int argument)
{
	if (house && type) {
		TeamTypeClass * team = new TeamTypeClass();

		if (team) {

			/*
			**	If a ground based reinforcement is desired, but the edge of the map that the
			**	reinforcement will arrive at is completely covered with water, then add
			**	a hover lander for transport.
			*/
			if (!another && (type->What_Am_I() == RTTI_UNITTYPE || type->What_Am_I() == RTTI_INFANTRYTYPE)) {

				/*
				**	Hover lander reinforcements can only arrive from the south. Yes, this is an
				**	arbitrary limitation, but that's the way it is (for now).
				*/
				if (house->Edge == SOURCE_SOUTH) {
					bool found = false;
					for (int index = Map.MapCellX; index < Map.MapCellX+Map.MapCellWidth-1; index++) {
						CELL cell = XY_Cell(index, Map.MapCellY+Map.MapCellHeight);
						if (Map[cell].Is_Generally_Clear() && Map[cell-MAP_CELL_W].Is_Generally_Clear()) {
							found = true;
							break;
						}
					}

					/*
					**	No land route was found for the reinforcement to drive itself onto the
					**	map. Assign it a hover lander. This presumes that if the south edge is
					**	non drivable, it will have water there instead. Risky, but is not a 
					**	problem with the current C&C maps.
					*/
					if (!found) {
						mission = TMISSION_NONE;
						another = &UnitTypeClass::As_Reference(UNIT_HOVER);
					}
				}

				if (!another) {
					mission = TMISSION_MOVECELL;
					argument = Map.Calculated_Cell(SOURCE_AIR, house->Class->House);
				}
			}

			/*
			**	If there is no overridden mission assign to this special reinforcement, then
			**	we must assign something. If not, the reinforcement will just sit at the edge
			**	of the map.
			*/
			if (!another && mission == TMISSION_NONE) {
				mission = TMISSION_MOVECELL;
				argument = Map.Calculated_Cell(SOURCE_AIR, house->Class->House);
			}

			/*
			**	Fill in the team characteristics.
			*/
			strcpy((char *)&team->IniName[0], "TEMP");
			team->IsReinforcable = false;
			team->IsTransient = true;
			team->ClassCount = 1;
			team->Class[0] = type;
			team->DesiredNum[0] = 1;
			team->MissionCount = 1;
			if (mission == TMISSION_NONE) {
				if (another && (another->What_Am_I() != RTTI_UNITTYPE || ((UnitTypeClass const *)another)->Type != UNIT_HOVER)) {
					team->MissionList[0].Mission	= TMISSION_UNLOAD;
					team->MissionList[0].Argument = WAYPT_REINF;
				}
			} else {
				team->MissionList[0].Mission	= mission;
				team->MissionList[0].Argument = argument;
			}
			team->House = house->Class->House;
			if (another) {
				team->ClassCount++;
				team->Class[1] = another;
				team->DesiredNum[1] = 1;
			}

			bool ok = Do_Reinforcements(team);
			if (!ok && GameToPlay == GAME_NORMAL) {
				delete team;
			}
			return(ok);
		}
	}
	return(false);
}


/*********************************************************************************************** 
 * Create_Air_Reinforcement -- Creates air strike reinforcement                                * 
 *                                                                                             * 
 *    This routine is used to launch an airstrike. It will create the necessary aircraft and   * 
 *    assign them to attack the target specified. This routine bypasses the normal             * 
 *    reinforcement logic since it doesn't need the sophistication of unloading and following  * 
 *    team mission lists.                                                                      * 
 *                                                                                             * 
 * INPUT:   house    -- The purpetrator of this air strike.                                    * 
 *                                                                                             * 
 *          air      -- The type of aircraft to make up this airstrike.                        * 
 *                                                                                             * 
 *          number   -- The number of aircraft in this airstrike.                              * 
 *                                                                                             * 
 *          mission  -- The mission to assign the aircraft.                                    * 
 *                                                                                             * 
 *          tarcom   -- The target to assign these aircraft.                                   * 
 *                                                                                             * 
 *          navcom   -- The navigation target to assign (if necessary).                        * 
 *                                                                                             * 
 * OUTPUT:  Returns the number of aircraft created for this airstrike.                         * 
 *                                                                                             * 
 * WARNINGS:   none                                                                            * 
 *                                                                                             * 
 * HISTORY:                                                                                    * 
 *   07/04/1995 JLB : Commented.                                                               * 
 *=============================================================================================*/
int Create_Air_Reinforcement(HouseClass *house, AircraftType air, int number, MissionType mission, TARGET tarcom, TARGET navcom)
{
	/*
	** Get a pointer to the class of the object that we are going to create.
	*/
	TechnoTypeClass const * type = (TechnoTypeClass *)&AircraftTypeClass::As_Reference(air);

	/*
	** Loop through the number of objects we are supposed to create and
	** 	create and place them on the map.
	*/
	int sub;
	for (sub = 0; sub < number; sub++) {

		/*
		** Create one of the required objects.  If this fails we could have
		** a real problem.
		*/
		ScenarioInit++;
		TechnoClass * obj = (TechnoClass *)type->Create_One_Of(house);
		ScenarioInit--;
		if (!obj) return(sub);

		/*
		** Flying objects always have the IsALoaner bit set.
		*/
		obj->IsALoaner = true;

		/* 
		** Find a cell for the object to come in on.  This is stolen from the
		** the code that handles a SOURCE_AIR in the normal logic.
		*/
		SourceType source = house->Edge;
		switch (source) {
			case SOURCE_NORTH:
			case SOURCE_EAST:
			case SOURCE_SOUTH:
			case SOURCE_WEST:
				break;

			default:
				source = SOURCE_NORTH;
				break;
		}
		CELL newcell = Map.Calculated_Cell(source, house->Class->House);

		/* 
		** Try and place the object onto the map.
		*/
		ScenarioInit++;
		int placed = obj->Unlimbo(Cell_Coord(newcell), DIR_N);
		ScenarioInit--;
		if (placed) {

			/*
			** If we suceeded in placing the obj onto the map then
			** now we need to give it a mission and destination.
			*/
			obj->Assign_Mission(mission);

			/*
			** If a navcom was specified then set it.
			*/
			if (navcom != TARGET_NONE) {
				obj->Assign_Destination(navcom);
			}

			/*
			** If a tarcom was specified then set it.
			*/
			if (tarcom != TARGET_NONE) {
				obj->Assign_Target(tarcom);
			}

			/*
			** Start the object into action.
			*/
			obj->Commence();
		} else {
			delete obj;
			sub--;
			return(sub);
		}
	}
	return(sub);
}
